#ifndef MARKETSIM_CPP
#define MARKETSIM_CPP

#include "marketSim.h"

//#define VERBOSE 0

marketSim::marketSim()
{

	//Pre  Cons: None.
	//Post Cons: Intializes all values to defaults.

	clock         = 0;
	processed     = 0;
	averageTime   = 0;
	longestTime   = 0;
	loiterer      = "";

	LNumbers[RL]  = 1;
	LNumbers[RC]  = 1;
	LNumbers[EC]  = 1;
	LNumbers[EL]  = 1;
	
	checkouts     = new checkout*[4];
	checkouts[RL] = new checkout[LNumbers[RL]];
	checkouts[RC] = new checkout[LNumbers[RC]];
	checkouts[EC] = new checkout[LNumbers[EC]];
	checkouts[EL] = new checkout[LNumbers[EL]]; 
	genisis       = NULL;
}

marketSim::~marketSim()
{
	//Pre  Cons: None.
	//Post Cons: Deletes dynamically allocated memory.

	delete genisis;
	delete checkouts;
}

void marketSim::seedG(string FNames, string LNames, int number, int start, int stop)
{
	//Pre  Cons: Takes string FNames, LNames, and int number, start, stop.
	//           Fnames and LNames are file locations, 0 > number < 999999,
	//           0 > start < stop, 0 > stop < 86400.
  	//Post Cons: Seeds genisis for customer creation.

	genisis = new customerGen(FNames, LNames, number, start, stop);
}

void marketSim::setCheckouts(int RLs, int RCs, int ELs, int ECs)
{
	//Pre  Cons: Takes int RLs, RCs, ELs, ECs that must
	//           be greater than zero.
	//Post Cons: Updates LNumbers and re-allocates memory for checkouts. 
	//           Should never be called during simulation.

	assert(RLs > 0);
	assert(RCs > 0);
	assert(ELs > 0);
	assert(ECs > 0);
	        
	LNumbers[RL]  = RLs;
        LNumbers[RC]  = RCs;
        LNumbers[EC]  = ECs;
        LNumbers[EL]  = ELs;

        checkouts     = new checkout*[4];
        checkouts[RL] = new checkout[LNumbers[RL]];
        checkouts[RC] = new checkout[LNumbers[RC]];
        checkouts[EC] = new checkout[LNumbers[EC]];
        checkouts[EL] = new checkout[LNumbers[EL]];
}


int marketSim::nextEvent()
{

	//Pre  Cons: None.
	//Post Cons: Finds next event and returns it's code, updates clock.
	//           --------------------------------------------
	//           | Code  |  Event                           |
	//           --------------------------------------------
	//           |  -1   |  Simulation over                 |
	//           |  -2   |  Customer arrives at store       |
	//           |  -3   |  Customer departs from contest   |
	//           |  -4   |  Customer departs from deli      |
	//           |  -5   |  Customer is done shopping       |
	//           | =< 0  |  Customer departs from checkout  |
	//           |       |  (event helps determine which    |
	//           |       |  checkout customer departs from) |
	//           --------------------------------------------	

	int event = -1;
	clock = genisis->nextTime();
	
	if(clock < 999999)
		event = -2;
	if(!contestLine.empty() && clock > contestLine.front().getCDepart()){
		clock = contestLine.front().getCDepart();
		event = -3;
	}
	if(!deliLine.empty() && clock > deliLine.front().getDDepart()){
		clock = deliLine.front().getDDepart();
		event = -4;
	}
	if(!shopping.empty() && clock > shopping.front().getDShopping()){
		clock = shopping.front().getDShopping();
		event = -5;
	}
	for(int i = 0; i < 4; i++){
		for(int j = 0; j < LNumbers[i]; j++){
			if(!checkouts[i][j].empty()){
				if( clock > checkouts[i][j].front().getLDepart()){
					clock = checkouts[i][j].front().getLDepart();
					event = j;
				}
			}
		}
	}

	return event;		
}

void marketSim::simulate()
{
	//Pre  Cons: Simulation must be seeded properly.
	//Post Cons: Runs simulation.

	int event = 0;
	assert(genisis != NULL);
	
	do{
		event = nextEvent();
			
		if(event >= 0)
			resolveCheckouts(event);
		else if(event == -2)
			resolveArrivals();
		else if(event == -3)
			resolveContest();
		else if(event == -4)
			resolveDeli();
		else if(event == -5)
			resolveShopping();
		else; /*WE DONE DO NOTHING*/
		
		#ifdef VERBOSE
			results();
		#endif

	}while(event != -1);
	
	#ifdef VERBOSE
	cout << "Contest Winners"                                     << endl
             << "First  Place: "  << contestLine.getFirstPlace()      << endl
             << "Second Place: "  << contestLine.getSecondPlace()     << endl
             << "==========================================="         << endl;
	#endif
}

void marketSim::resolveArrivals()
{
	//Pre  Cons: None.
	//Post Cons: Resolves new arrivals and inqueues them into proper
	//           line with the current time.

	customer arrival;
	genisis->next(arrival);

	if(arrival.getContest())
		contestLine.inqueue(arrival, clock);
	else if(arrival.getDeli())
		deliLine.inqueue(arrival, clock);
	else
		shopping.inqueue(arrival, clock);
}

void marketSim::resolveContest()
{
	//Pre  Cons: None.
	//Post Cons: Resolves contest departures and inqueues them into
	//           proper line with current time.

	customer departure = contestLine.front();
	contestLine.dequeue(clock);
	if(departure.getDeli())
		deliLine.inqueue(departure, clock);
	else
		shopping.inqueue(departure, clock);
}

void marketSim::resolveDeli()
{
	//Pre  Cons: None.
	//Post Cons: Resolves deli departures and inqueues them into proper
	//           line with current time.

	customer departure = deliLine.front();
	deliLine.dequeue(clock);	     
	
	shopping.inqueue(departure, clock);
}

void marketSim::resolveCheckouts(int index)
{
	//Pre  Cons: Takes an int index >= 0.
	//Post Cons: Locates checkout with customer departure equal to the
	//           current time. Resolves that deparutre and processes
	//           the departing customer. 

        int i;
	for(i = 0; i < 4; i++){
		if(LNumbers[i] > index){
			if(!checkouts[i][index].empty()){
				if(checkouts[i][index].front().getLDepart() == clock){
					break;
				}
			}
		}
	}
	
	customer departure = checkouts[i][index].front();
	checkouts[i][index].dequeue(clock);

	departure.setDepart(clock);
	process(departure);
}

void marketSim::resolveShopping()
{
	//Pre  Cons: None.
	//Post Cons: Resolves customers who are done shopping, locates the
	//           shortest checkout of customers lineType and inqueues 
	//           customer into that checkout with the current time.

        customer departure = shopping.front();
        shopping.dequeue(clock);
	
	int bestLine = 0;
	int shortest = 999999;

	for(int i = 0; i < LNumbers[departure.getLType()]; i++){
		if(checkouts[departure.getLType()][i].size() < shortest){
			shortest = checkouts[departure.getLType()][i].size();
			bestLine = i;
		} 
	}

	checkouts[departure.getLType()][bestLine].inqueue(departure, clock);
}

void marketSim::results()
{
	//Pre  Cons: None.
	//Post Cons: Dumps current statistics to the screen.
	
	cout                                                          << endl
	     << "==========================================="         << endl
	     << "Overall Statistics"                                  << endl
	     << "==========================================="         << endl;

	#ifndef VERBOSE
	cout << "Contest Winners"                                     << endl 
	     << "First  Place: "  << contestLine.getFirstPlace()      << endl
	     << "Second Place: "  << contestLine.getSecondPlace()     << endl
	     << "-------------------------------------------"         << endl;
	#endif

	cout << "Processed               : " << processed             << endl
	     << "Average time in store   : " << averageTime           << endl
	     << "Longest time in store   : " << longestTime           << endl
	     << "Who stayed the longest  : " << loiterer              << endl
	     << "Most customers shopping : " << shopping.getLLength() << endl << endl 
	     << "==========================================="         << endl
	     << "Contest Statistics"                                  << endl
	     << "==========================================="         << endl;	
	     contestLine.display();
	cout                                                          << endl                                                         
	     << "==========================================="         << endl
	     << "Deli Statistics"                                     << endl
	     << "==========================================="         << endl;
	     deliLine.display();
	cout                                                          << endl
	     << "==========================================="         << endl
	     << "Checkout Line Statistics"                            << endl
	     << "==========================================="         << endl << endl;
	     for(int i = 0; i < LNumbers[RL]; i++){
		cout << "Regular Line #" << i+1                       << endl
		     << "-------------------------------------------" << endl;
		     checkouts[RL][i].display();
		cout                                                  << endl;
	     }
             for(int i = 0; i < LNumbers[RC]; i++){
                cout << "Regular Cash #" << i+1                       << endl
                     << "-------------------------------------------" << endl;
                     checkouts[RC][i].display();
                cout                                                  << endl;
             }
             for(int i = 0; i < LNumbers[EC]; i++){
                cout << "Express cash #" << i+1                       << endl
                     << "-------------------------------------------" << endl;
                     checkouts[EC][i].display();
                cout                                                  << endl;
             }
             for(int i = 0; i < LNumbers[EL]; i++){
                cout << "Express Line #" << i+1                       << endl
                     << "-------------------------------------------" << endl;
                     checkouts[EL][i].display();
                cout                                                  << endl;
             }
        cout << "==========================================="         << endl;

}	

void marketSim::process(customer c)
{
	//Pre  Cons: Takes a customer c.
	//Post Cons: Updates processed, averageTime, longestTime, and loiterer.
	
	int newTime = c.getDepart() - c.getArrive();

        averageTime *= processed;
        averageTime += newTime;
        processed++;
        averageTime /= processed;

        if(newTime > longestTime){
                longestTime = newTime;
                loiterer = c.getFName() + " " + c.getLName();
        }
}

#endif
